/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <assert.h>
//#include <cutils/log.h>
#include <jni.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "../include/pinyinime.h"
#include "../include/sync.h"
#include "../include/userdict.h"

#ifdef __cplusplus
extern "C" {
#endif

using namespace ime_pinyin;

#define RET_BUF_LEN 256

static char16 retbuf[RET_BUF_LEN];
static char16 (*predict_buf)[kMaxPredictSize + 1] = NULL;
static size_t predict_len;

static Sync sync_worker;

static struct file_descriptor_offsets_t {
	jclass mClass;
	jfieldID mDescriptor;
} gFileDescriptorOffsets;

JNIEXPORT jboolean JNICALL nativeImOpenDecoder(JNIEnv* env, jclass jclazz,
		jbyteArray fn_sys_dict, jbyteArray fn_usr_dict) {
	jbyte *fsd = (*env).GetByteArrayElements(fn_sys_dict, 0);
	jbyte *fud = (*env).GetByteArrayElements(fn_usr_dict, 0);

	jboolean jret = JNI_FALSE;

	if (im_open_decoder((const char*) fsd, (const char*) fud))
		jret = JNI_TRUE;

	(*env).ReleaseByteArrayElements(fn_sys_dict, fsd, 0);
	(*env).ReleaseByteArrayElements(fn_usr_dict, fud, 0);

	return jret;
}

JNIEXPORT jboolean JNICALL nativeImOpenDecoderFd(JNIEnv* env, jclass jclazz,
		jobject fd_sys_dict, jlong startoffset, jlong length,
		jbyteArray fn_usr_dict) {
	jint fd = env->GetIntField(fd_sys_dict, gFileDescriptorOffsets.mDescriptor);
	jbyte *fud = (*env).GetByteArrayElements(fn_usr_dict, 0);

	jboolean jret = JNI_FALSE;

	int newfd = dup(fd);
	if (im_open_decoder_fd(newfd, startoffset, length, (const char*) fud))
		jret = JNI_TRUE;

	close(newfd);

	(*env).ReleaseByteArrayElements(fn_usr_dict, fud, 0);

	return jret;
}

JNIEXPORT void JNICALL nativeImSetMaxLens(JNIEnv* env, jclass jclazz,
		jint max_sps_len,
		jint max_hzs_len) {
	im_set_max_lens(static_cast<size_t>(max_sps_len),
			static_cast<size_t>(max_hzs_len));
	return;
}

JNIEXPORT jboolean JNICALL nativeImCloseDecoder(JNIEnv* env, jclass jclazz) {
	im_close_decoder();
	return JNI_TRUE;
}

JNIEXPORT jint JNICALL nativeImSearch(JNIEnv* env, jclass jclazz,
		jbyteArray pybuf, jint pylen) {
	jbyte *array_body = (*env).GetByteArrayElements(pybuf, 0);

	jint jret = 0;
	if (NULL != array_body) {
		jret = im_search((const char*) array_body, pylen);
	}

	(*env).ReleaseByteArrayElements(pybuf, array_body, 0);

	return jret;
}

JNIEXPORT jint JNICALL nativeImDelSearch(JNIEnv* env, jclass jclazz, jint pos,
		jboolean is_pos_in_splid, jboolean clear_fixed_this_step) {
	return im_delsearch(pos, is_pos_in_splid, clear_fixed_this_step);
}

JNIEXPORT void JNICALL nativeImResetSearch(JNIEnv* env, jclass jclazz) {
	im_reset_search();
	return;
}

JNIEXPORT jint JNICALL nativeImAddLetter(JNIEnv *env, jclass clazz, jbyte ch) {
	return im_add_letter(ch);
}

JNIEXPORT jstring JNICALL nativeImGetPyStr(JNIEnv* env, jclass jclazz,
		jboolean decoded) {
	size_t py_len;
	const char *py = im_get_sps_str(&py_len); // py_len gets decoded length
	assert(NULL != py);
	if (!decoded)
		py_len = strlen(py);

	const unsigned short *spl_start;
	size_t len;
	len = im_get_spl_start_pos(spl_start);

	size_t i;
	for (i = 0; i < py_len; i++)
		retbuf[i] = py[i];
	retbuf[i] = (char16) '\0';

	jstring retstr = (*env).NewString((unsigned short*) retbuf, i);
	return retstr;
}

JNIEXPORT jint JNICALL nativeImGetPyStrLen(JNIEnv* env, jclass jclazz,
		jboolean decoded) {
	size_t py_len;
	const char *py = im_get_sps_str(&py_len); // py_len gets decoded length
	assert(NULL != py);
	if (!decoded)
		py_len = strlen(py);
	return py_len;
}

JNIEXPORT jintArray JNICALL nativeImGetSplStart(JNIEnv* env, jclass jclazz) {
	const unsigned short *spl_start;
	size_t len;

	// There will be len + 1 elements in the buffer when len > 0.
	len = im_get_spl_start_pos(spl_start);

	jintArray arr = (*env).NewIntArray(len + 2);
	jint *arr_body = (*env).GetIntArrayElements(arr, 0);
	assert(NULL != arr_body);
	arr_body[0] = len; // element 0 is used to store the length of buffer.
	for (size_t i = 0; i <= len; i++)
		arr_body[i + 1] = spl_start[i];

	(*env).ReleaseIntArrayElements(arr, arr_body, 0);

	return arr;
}

JNIEXPORT jstring JNICALL nativeImGetChoice(JNIEnv *env, jclass clazz,
		jint candidateId) {
	jstring retstr;
	if (im_get_candidate(candidateId, retbuf, RET_BUF_LEN)) {
		retstr = (*env).NewString(retbuf, utf16_strlen(retbuf));
		return retstr;
	} else {
		retstr = (*env).NewString((unsigned short*) retbuf, 0);
		return retstr;
	}
}

JNIEXPORT jint JNICALL nativeImChoose(JNIEnv *env, jclass clazz,
		jint choice_id) {
	return im_choose(choice_id);
}

JNIEXPORT jint JNICALL nativeImCancelLastChoice(JNIEnv *env, jclass clazz) {
	return im_cancel_last_choice();
}

JNIEXPORT jint JNICALL nativeImGetFixedLen(JNIEnv *env, jclass clazz) {
	return im_get_fixed_len();
}

JNIEXPORT jboolean JNICALL nativeImCancelInput(JNIEnv *env, jclass clazz) {
	if (im_cancel_input())
		return JNI_TRUE;

	return JNI_FALSE;
}

JNIEXPORT jboolean JNICALL nativeImFlushCache(JNIEnv *env, jclass clazz) {
	im_flush_cache();
	return JNI_TRUE;
}

JNIEXPORT jint JNICALL nativeImGetPredictsNum(JNIEnv *env, jclass clazz,
		jstring fixed_str) {
	char16 *fixed_ptr = (char16*) (*env).GetStringChars(fixed_str, false);
	size_t fixed_len = (size_t)(*env).GetStringLength(fixed_str);

	char16 fixed_buf[kMaxPredictSize + 1];

	if (fixed_len > kMaxPredictSize) {
		fixed_ptr += fixed_len - kMaxPredictSize;
		fixed_len = kMaxPredictSize;
	}
	utf16_strncpy(fixed_buf, fixed_ptr, fixed_len);
	fixed_buf[fixed_len] = (char16) '\0';

	predict_len = im_get_predicts(fixed_buf, predict_buf);

	(*env).ReleaseStringChars(fixed_str, fixed_ptr);

	return predict_len;
}

JNIEXPORT jstring JNICALL nativeImGetPredictItem(JNIEnv *env, jclass clazz,
		jint predict_no) {
	jstring retstr;

	if (predict_no < 0 || (size_t) predict_no >= predict_len) {
		retstr = (*env).NewString((unsigned short*) predict_buf[0], 0);
	} else {
		retstr = (*env).NewString((unsigned short*) predict_buf[predict_no],
				utf16_strlen(predict_buf[predict_no]));
	}
	return retstr;
}

JNIEXPORT jboolean JNICALL nativeSyncBegin(JNIEnv *env, jclass clazz,
		jbyteArray dict_file) {
	jbyte *file_name = (*env).GetByteArrayElements(dict_file, 0);

	jboolean jret = JNI_FALSE;
	if (true == sync_worker.begin((const char *) file_name))
		jret = JNI_TRUE;

	(*env).ReleaseByteArrayElements(dict_file, file_name, 0);

	return jret;
}

JNIEXPORT jboolean JNICALL nativeSyncFinish(JNIEnv *env, jclass clazz) {
	sync_worker.finish();
	return JNI_TRUE;
}

JNIEXPORT jint JNICALL nativeSyncGetCapacity(JNIEnv *env, jclass clazz) {
	return sync_worker.get_capacity();
}

JNIEXPORT jint JNICALL nativeSyncPutLemmas(JNIEnv *env, jclass clazz,
		jstring tomerge) {

	char16 *ptr = (char16*) (*env).GetStringChars(tomerge, NULL);
	int len = (size_t)(*env).GetStringLength(tomerge);

	int added = sync_worker.put_lemmas(ptr, len);

	(*env).ReleaseStringChars(tomerge, ptr);

	return added;
}

JNIEXPORT jstring JNICALL nativeSyncGetLemmas(JNIEnv *env, jclass clazz) {

	int len = sync_worker.get_lemmas(retbuf, RET_BUF_LEN);
	if (len == 0)
		return NULL;
	jstring retstr;
	retstr = (*env).NewString((unsigned short*) retbuf, len);
	return retstr;
}

JNIEXPORT jint JNICALL nativeSyncGetLastCount(JNIEnv *env, jclass clazz) {
	return sync_worker.get_last_got_count();
}

JNIEXPORT jint JNICALL nativeSyncGetTotalCount(JNIEnv *env, jclass clazz) {
	return sync_worker.get_total_count();
}

JNIEXPORT jboolean JNICALL nativeSyncClearLastGot(JNIEnv *env, jclass clazz) {
	sync_worker.clear_last_got();
	return JNI_TRUE;
}

/**
 * Table of methods associated with a single class.
 */
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
/* ------Functions for Pinyin-to-hanzi decoding begin--------->> */
{ "nativeImOpenDecoder", "([B[B)Z", (void*) nativeImOpenDecoder }, {
		"nativeImOpenDecoderFd", "(Ljava/io/FileDescriptor;JJ[B)Z",
		(void*) nativeImOpenDecoderFd }, { "nativeImSetMaxLens", "(II)V",
		(void*) nativeImSetMaxLens }, { "nativeImCloseDecoder", "()Z",
		(void*) nativeImCloseDecoder }, { "nativeImSearch", "([BI)I",
		(void*) nativeImSearch }, { "nativeImDelSearch", "(IZZ)I",
		(void*) nativeImDelSearch }, { "nativeImResetSearch", "()V",
		(void*) nativeImResetSearch }, { "nativeImAddLetter", "(B)I",
		(void*) nativeImAddLetter }, { "nativeImGetPyStr",
		"(Z)Ljava/lang/String;", (void*) nativeImGetPyStr }, {
		"nativeImGetPyStrLen", "(Z)I", (void*) nativeImGetPyStrLen }, {
		"nativeImGetSplStart", "()[I", (void*) nativeImGetSplStart },
		{ "nativeImGetChoice", "(I)Ljava/lang/String;",
				(void*) nativeImGetChoice }, { "nativeImChoose", "(I)I",
				(void*) nativeImChoose }, { "nativeImCancelLastChoice", "()I",
				(void*) nativeImCancelLastChoice }, { "nativeImGetFixedLen",
				"()I", (void*) nativeImGetFixedLen }, {
				"nativeImGetPredictsNum", "(Ljava/lang/String;)I",
				(void*) nativeImGetPredictsNum }, { "nativeImGetPredictItem",
				"(I)Ljava/lang/String;", (void*) nativeImGetPredictItem }, {
				"nativeImCancelInput", "()Z", (void*) nativeImCancelInput }, {
				"nativeImFlushCache", "()Z", (void*) nativeImFlushCache },
		/* <<----Functions for Pinyin-to-hanzi decoding end------------- */

		/* ------Functions for sync begin----------------------------->> */
		{ "nativeSyncBegin", "([B)Z", (void*) nativeSyncBegin }, {
				"nativeSyncFinish", "()Z", (void*) nativeSyncFinish }, {
				"nativeSyncPutLemmas", "(Ljava/lang/String;)I",
				(void*) nativeSyncPutLemmas }, { "nativeSyncGetLemmas",
				"()Ljava/lang/String;", (void*) nativeSyncGetLemmas },
		{ "nativeSyncGetLastCount", "()I", (void*) nativeSyncGetLastCount }, {
				"nativeSyncGetTotalCount", "()I",
				(void*) nativeSyncGetTotalCount }, { "nativeSyncClearLastGot",
				"()Z", (void*) nativeSyncClearLastGot }, {
				"nativeSyncGetCapacity", "()I", (void*) nativeSyncGetCapacity },
/* <<----Functions for sync end--------------------------------- */
};

/*
 * Register several native methods for one class.
 */
static int registerNativeMethods(JNIEnv* env, const char* className,
		JNINativeMethod* gMethods, int numMethods) {
	jclass clazz;

	clazz = (*env).FindClass(className);
	if (clazz == NULL) {
		return JNI_FALSE;
	}
	if ((*env).RegisterNatives(clazz, gMethods, numMethods) < 0) {
		return JNI_FALSE;
	}

	clazz = env->FindClass("java/io/FileDescriptor");
	//  LOG_FATAL_IF(clazz == NULL, "Unable to find Java class java.io.FileDescriptor");
	gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
	gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor",
			"I");
	// LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL,
	//        "Unable to find descriptor field in java.io.FileDescriptor");

	return JNI_TRUE;
}

/*
 * Register native methods for all classes we know about.
 */
static int registerNatives(JNIEnv* env) {
	if (!registerNativeMethods(env,
			"com/keanbin/pinyinime/PinyinDecoderService", gMethods,
			sizeof(gMethods) / sizeof(gMethods[0])))
		return JNI_FALSE;

	return JNI_TRUE;
}

/*
 * Set some test stuff up.
 *
 * Returns the JNI version on success, -1 on failure.
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
	JNIEnv* env = NULL;
	jint result = -1;

	if ((*vm).GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
		goto bail;
	}
	assert(env != NULL);

	if (!registerNatives(env)) {
		goto bail;
	}

	/* success -- return valid version number */
	result = JNI_VERSION_1_4;

	bail: return result;
}

#ifdef __cplusplus
}
#endif
